# package files

# import (
# 	"fmt"
# 	"io"
# 	"os"
# 	"path/filepath"
# 	"strings"
# 	"syscall"

# 	"github.com/aptly-dev/aptly/aptly"
# 	"github.com/aptly-dev/aptly/utils"
# )
import os
from shutil import copy
from enum import Enum
from files import package_pool
import utils
# // PublishedStorage abstract file system with public dirs (published repos)
class PublishedStorage :
    rootPath     =''
    linkMethod  = 0
    verifyMethod =0
    # // PublicPath returns root of public part
    def PublicPath(storage):
        return storage.rootPath


    # // MkDir creates directory recursively under public path
    def  MkDir(storage,path) :
        try:
            os.makedirs(os.path.join(storage.rootPath, path),mode=0o777)
        except:
            print('创建文件夹错误，可忽略')
        return 


    # // PutFile puts file into published storage at specified path
    def  PutFile(storage,path :str, sourceFilename:str) :
        copy(sourceFilename,os.path.join(storage.rootPath, path))
        return 


    # // Remove removes single file under public path
    def  Remove(storage,path :str) :
        if len(path) <= 0 :
            raise Exception("trying to remove empty path")
        
        filepath = os.path.join(storage.rootPath, path)
        return os.remove(filepath)


    # // RemoveDirs removes directory structure under public path
    def  RemoveDirs(storage,path :str):
        if len(path) <= 0 :
            raise Exception("trying to remove the root directory")
        
        filepath = os.path.join(storage.rootPath, path)
        print("Removing {}...\n".format( filepath))
        
        return os.rmdir(filepath)


    # // LinkFromPool links package file from pool to dist's pool location
    # //
    # // publishedDirectory is desired location in pool (like prefix/pool/component/liba/libav/)
    # // sourcePool is instance of aptly.PackagePool
    # // sourcePath is a relative path to package file in package pool
    # //
    # // LinkFromPool returns relative path for the published file to be included in package index
    def LinkFromPool(storage,publishedPrefix, publishedRelPath,publishedDirectory, fileName :str, sourcePool:package_pool.PackagePool ,
        sourcePath :str, sourceChecksums :utils.checksum.ChecksumInfo, force:bool):
        baseName = os.path.basename(fileName)
        poolPath = os.path.join(storage.rootPath, publishedPrefix, publishedRelPath, os.path.dirname(fileName))
        try:
            os.makedirs(poolPath,mode=0o777)
        except Exception as e:
            print(e)
        

        dstStat=None
        srcStat =None
        dstStatErrorStatus=False
        try:
            dstStat = os.stat(os.path.join(poolPath, baseName))
        except:
            dstStatErrorStatus=True
            pass
        if not dstStatErrorStatus:
            try:
                # // already exists, check source file
                srcStat= sourcePool.Stat(sourcePath)
                if storage.linkMethod == linkMethod.LinkMethodCopy :
                    if storage.verifyMethod == VerificationMethod.VerificationMethodFileSize :
                        # // if source and destination have the same size, no need to copy
                        if srcStat.Size() == dstStat.Size() :
                            return None
                        
                    else :
                        # // if source and destination have the same checksums, no need to copy
                        dstMD5 = utils.checksum.MD5ChecksumForFile(os.path.join(poolPath, baseName))

                        if dstMD5 is None :
                            raise
                        if dstMD5 == sourceChecksums.MD5 :
                            return None
                else :

                    # // if source and destination inodes match, no need to link

                    # // Symlink can point to different filesystem with identical inodes
                    # // so we have to check the device as well.
                    if dstStat.st_ino == srcStat.st_ino and dstStat.st_dev == srcStat.st_dev: 
                        return None
                    

                # // source and destination have different inodes, if !forced, this is fatal error
                if not force :
                    return ("error linking file to {}: file already exists and is different".format(os.path.join(poolPath, baseName)))
                    
                

                # // forced, so remove destination
                os.remove(os.path.join(poolPath, baseName))
            except Exception as e:
                print(e)
                return "source file doesn't exist?"

        # // destination doesn't exist (or forced), create link or copy
        if storage.linkMethod == linkMethod.LinkMethodCopy :
            copy(sourcePath,os.path.join(poolPath, baseName))
        elif storage.linkMethod == linkMethod.LinkMethodSymLink :
            err = sourcePool.Symlink(sourcePath, os.path.join(poolPath, baseName))
        else :
            err = sourcePool.Link(sourcePath, os.path.join(poolPath, baseName))
        

        return err


    # // Filelist returns list of files under prefix
    def Filelist(storage ,prefix :str):
        root = os.path.join(storage.rootPath, prefix)
        result = []

        for rootDir, dirs, files in os.walk(root):
            for name in files:
                result.append( os.path.join(rootDir,name))


        # if len(result)==0:
        # 	# // file path doesn't exist, consider it empty
        # 	return []
        
        return result


    # // RenameFile renames (moves) file
    def  RenameFile(storage ,oldName, newName :str):
        return os.rename(os.path.join(storage.rootPath, oldName), os.path.join(storage.rootPath, newName))


    # // SymLink creates a symbolic link, which can be read with ReadLink
    def  SymLink(storage ,src :str, dst :str) :
        return os.Symlink(os.path.join(storage.rootPath, src), os.path.join(storage.rootPath, dst))


    # // HardLink creates a hardlink of a file
    def  HardLink(storage ,src :str, dst :str):
        return os.Link(os.path.join(storage.rootPath, src), os.path.join(storage.rootPath, dst))


    # // FileExists returns true if path exists
    def  FileExists(storage ,path :str) :
        return os.path.exists(os.path.join(storage.rootPath, path))
            


    # // ReadLink returns the symbolic link pointed to by path (relative to storage
    # // root)
    def ReadLink(storage ,path :str):
        absPath, = os.readlink(os.path.join(storage.rootPath, path))
        
        return os.path.relpath(storage.rootPath, absPath)





# // Check interfaces
# var (
# 	_ aptly.PublishedStorage           = (*PublishedStorage)(None)
# 	_ aptly.FileSystemPublishedStorage = (*PublishedStorage)(None)
# )

# // Constants defining the type of creating links
class linkMethod(Enum):
    LinkMethodHardLink =1
    LinkMethodSymLink=2
    LinkMethodCopy=3


# // Constants defining the type of file verification for LinkMethodCopy
class VerificationMethod(Enum):
    VerificationMethodChecksum =1
    VerificationMethodFileSize=2

# // NewPublishedStorage creates new instance of PublishedStorage which specified root
def NewPublishedStorage(root:str, linkMethodStr:str, verifyMethod:str):
    # // Ensure linkMethod is one of 'hardlink', 'symlink', 'copy'
    verifiedLinkMethod=None

    if linkMethodStr== "copy" :
        verifiedLinkMethod = linkMethod.LinkMethodCopy
    elif linkMethodStr== "symlink" :
        verifiedLinkMethod = linkMethod.LinkMethodSymLink
    else :
        verifiedLinkMethod = linkMethod.LinkMethodHardLink
    

    # var verifiedVerifyMethod uint
    verifiedVerifyMethod=None
    if  (verifyMethod== "size") :
        verifiedVerifyMethod = VerificationMethod.VerificationMethodFileSize
    else :
        verifiedVerifyMethod = VerificationMethod.VerificationMethodChecksum
    publishedStorage=PublishedStorage()
    publishedStorage.rootPath=root
    publishedStorage.linkMethod=verifiedLinkMethod
    publishedStorage.verifyMethod=verifiedVerifyMethod
    return publishedStorage



